{*******************************************************}
{                                                       }
{       DevOnRails                                      }
{                                                       }
{       Copyright (C) 2012 Allan Gomes                  }
{                                                       }
{*******************************************************}

unit Rails.ApplicationController.Base;

interface

uses System.SysUtils, System.Classes, Datasnap.DSServer, Datasnap.DSAuth, System.Generics.Collections,
     Aurelius.Engine.ObjectManager, RTTI, Rails.ActiveRecord.Base,
     Rails.ActiveRecord.Attributes, Aurelius.Criteria.Base, {Cliente.Model,}
     Aurelius.Criteria.Expression, Aurelius.Mapping.Attributes;

type
{$REGION 'TController'}
  {$METHODINFO ON}
  TController = class(TComponent)
  end;
  {$METHODINFO OFF}
{$ENDREGION}

  TSaveAction = (saInsert, saUpdate);
  TControllerClass = class of TController;

{$REGION 'TActiveController'}
  TActiveController<T: TActiveRecord, constructor> = class(TController)
  private
    FManager: TObjectManager;
    function CreateManager:TObjectManager;
    function FieldIsEmpty(Value: TValue): Boolean;
  protected
    function Validation(ARecord: T; out Error: string): Boolean; virtual;
  public
    constructor Create(AOwner: TComponent); override;
    function GetNewRecord: T; virtual;
    function LoadAll(out Error: string): TObjectList<T>; virtual;
    function FindKey(const Id: Integer; out ARecord: T; out Error: string): Boolean; virtual;
    function Save(ARecord: T; out Error: string): Boolean; virtual;
    function Delete(ARecord: T; out Error: string): Boolean; virtual;
    function DeleteById(const Id: String; out Error: string): Boolean; virtual;
    destructor Destroy; override;
  end;
{$ENDREGION}

implementation

uses
  System.StrUtils, System.TypInfo, Rails.Consts,
  Rails.Utils, Rails.MetaData, Aurelius.Mapping.Explorer, Rails.DBConnection;

{$REGION 'TActiveController'}
{ TdsmActiveController<T> }

constructor TActiveController<T>.Create(AOwner: TComponent);
begin
  inherited;
  FManager := CreateManager;
end;

function TActiveController<T>.DeleteById(const Id: String; out Error: string): Boolean;
var
  RecordDelete: T;
begin
  Result := False;
  try
    try
      RecordDelete := FManager.Find<T>(Id);
      if RecordDelete <> nil then
        FManager.Remove(RecordDelete);
      Result := True;
    except on E: Exception do
      Error := TRailsUtils.IndentifyError(E.Message);
    end;
  finally
    FManager.OwnsObjects := False;
  end;
end;

destructor TActiveController<T>.Destroy;
begin
  inherited;
  FreeAndNil(FManager);
end;

function TActiveController<T>.CreateManager: TObjectManager;
begin
  Result := TDBConnection.GetInstance.CreateObjectManager;
end;

function TActiveController<T>.Delete(ARecord: T; out Error: string): Boolean;
var
  Ctx: TRttiContext;
  Typ: TRttiType;
  F: TRttiField;
  RecordDelete: T;
begin
  Result := False;
  try
    try
      Ctx := TRttiContext.Create;
      Typ := Ctx.GetType(ARecord.ClassType);
      F := Typ.GetField(FIELD_ID);
      RecordDelete := FManager.Find<T>(F.GetValue(TObject(ARecord)).AsInteger);
      if RecordDelete <> nil then
        FManager.Remove(RecordDelete);
      Result := True;
    except on E: Exception do
      Error := E.Message;
    end;
  finally
    if Typ <> nil then
      FreeAndNil(Typ);
    FManager.OwnsObjects := False;
  end;
end;

function TActiveController<T>.FindKey(const Id: Integer; out ARecord: T;
  out Error: string): Boolean;
begin
  Result := False;
  try
    try
      ARecord := FManager.Find<T>(Id);
      if ARecord = nil then
      begin
        Error := TRailsUtils.IndentifyExpected(ID_NOT_FOUND);
        Exit;
      end;
      Result := True;
    except on E: Exception do
      Error := TRailsUtils.IndentifyError(E.Message);
    end;
  finally
    FManager.OwnsObjects := False;
  end;
end;

function TActiveController<T>.GetNewRecord: T;
begin
  Result := T.Create;
end;

function TActiveController<T>.LoadAll(out Error: string): TObjectList<T>;
begin
  try
    Result := FManager.FindAll<T>;
  except on E: Exception do
    Error := TRailsUtils.IndentifyError(E.Message);
  end;
end;

function TActiveController<T>.Save(ARecord: T; out Error: string): Boolean;
begin
  Result := False;
  try
    try
      if not Validation(ARecord, Error) then
        Exit;
      if not FManager.IsAttached(ARecord) then
        FManager.SaveOrUpdate(ARecord);
      FManager.Flush;
      Result := True;
    except on E: Exception do
      Error := TRailsUtils.IndentifyError(E.Message);
    end;
  finally
    FManager.OwnsObjects := False;
  end;
end;

function TActiveController<T>.FieldIsEmpty(Value: TValue): Boolean;
begin
  case Value.Kind of
    tkString, tkUString, tkWString: Result := (Value.AsString = EmptyStr);
    tkInteger, tkInt64: Result := (Value.AsInteger = 0);
    tkFloat: Result := (Value.AsExtended = 0);
  end;
end;

function TActiveController<T>.Validation(ARecord: T;
  out Error: string): Boolean;
var
  Manager: TObjectManager;
  Results: TObjectList<T>;
  V: TValue;
  Col: TRailsColumn;
begin
  Result := False;
  Manager := nil;
  try
    Manager := CreateManager;
    Manager.OwnsObjects := True;

    for Col in TRailsExplorer.GetIntance.GetColumns(ARecord.ClassType) do
    begin
      if TColumnProp.Required in Col.ColumnProperties then
      begin
        V := TMappingExplorer.DefaultInstance.GetMemberValue(ARecord, Col.ColumnInfo.Optimization);
        if FieldIsEmpty(V) then
        begin
          if Col.DisplayName <> EmptyStr then
            Error := Error+TRailsUtils.IndentifyExpected(Format(FIELD_REQUIRED, [Col.DisplayName]))+LINE_BREAK
          else
            Error := Error+TRailsUtils.IndentifyExpected(Format(FIELD_REQUIRED, [Col.ColumnName]))+LINE_BREAK;
        end;
      end;
      if TColumnProp.Unique in Col.ColumnProperties then
      begin
        Results := Manager.CreateCriteria<T>
                   .Add(TExpression.Eq(Col.ColumnName, V.AsVariant))
                   .List;
        if Results.Count > 0 then
        begin

          if Col.DisplayName <> EmptyStr then
            Error := Error+TRailsUtils.IndentifyExpected(Format(FIELD_UNIQUE, [Col.DisplayName]))+LINE_BREAK
          else
            Error := Error+TRailsUtils.IndentifyExpected(Format(FIELD_UNIQUE, [Col.ColumnName]))+LINE_BREAK;
        end;
        Results.Clear;
        if Results <> nil then
          FreeAndNil(Results);
      end;
    end;
    Result := (Error = EmptyStr);
    if not Result then
    IfThen(Error <> EmptyStr, Copy(Error, 0, Length(Error)-Length(LINE_BREAK)));
  finally
    Manager.Clear;
    if Manager <> nil then
      FreeAndNil(Manager);
  end;
end;
{$ENDREGION}

end.
